﻿
using Tszy.Unlimited.Base.Exceptions;
using Tszy.Unlimited.Base.Extensions;

namespace Tszy.Unlimited.Base.Common;

/// <summary>
/// 数学计算。
/// </summary>
public static class MathT
{
    #region 阶乘

    /// <summary>
    /// 计算指定数字的阶乘。
    /// </summary>
    /// <remarks>
    /// n! = 1 × 2 × 3 × …… × （n - 1） × n
    /// 示例：
    /// 5! = 1 × 2 × 3 × 4 × 5 = 120
    /// </remarks>
    /// <param name="source"></param>
    /// <returns></returns>
    public static long Factorial(long source)
    {
        return source.Factorial();
    }

    /// <summary>
    /// 计算指定数字的阶乘。
    /// </summary>
    /// <remarks>
    /// n! = 1 × 2 × 3 × …… × （n - 1） × n
    /// 示例：
    /// 5! = 1 × 2 × 3 × 4 × 5 = 120
    /// </remarks>
    /// <param name="source"></param>
    /// <returns></returns>
    public static int Factorial(this int source)
    {
        return source.Factorial();
    }

    /// <summary>
    /// 计算指定数字的阶乘。
    /// </summary>
    /// <remarks>
    /// n! = 1 × 2 × 3 × …… × （n - 1） × n
    /// 示例：
    /// 5! = 1 × 2 × 3 × 4 × 5 = 120
    /// </remarks>
    /// <param name="source"></param>
    /// <returns></returns>
    public static decimal Factorial(this decimal source)
    {
        return source.Factorial();
    }

    /// <summary>
    /// 计算指定数字的阶乘。
    /// </summary>
    /// <remarks>
    /// n! = 1 × 2 × 3 × …… × （n - 1） × n
    /// 示例：
    /// 5! = 1 × 2 × 3 × 4 × 5 = 120
    /// </remarks>
    /// <param name="source"></param>
    /// <returns></returns>
    public static float Factorial(this float source)
    {
        return source.Factorial();
    }

    /// <summary>
    /// 计算指定数字的阶乘。
    /// </summary>
    /// <remarks>
    /// n! = 1 × 2 × 3 × …… × （n - 1） × n
    /// 示例：
    /// 5! = 1 × 2 × 3 × 4 × 5 = 120
    /// </remarks>
    /// <param name="source"></param>
    /// <returns></returns>
    public static double Factorial(this double source)
    {
        return source.Factorial();
    }

    #endregion 阶乘

    #region 排列

    /// <summary>
    /// 计算排列数。
    /// </summary>
    /// <param name="n">元素总数。</param>
    /// <param name="m">取出的元素个数。</param>
    /// <returns>排列数。</returns>
    public static long A(long n, long m)
    {
        return n.Arrangement(m);
    }

    /// <summary>
    /// 计算排列数。
    /// </summary>
    /// <param name="n">元素总数。</param>
    /// <param name="m">取出的元素个数。</param>
    /// <returns>排列数。</returns>
    public static int A(int n, int m)
    {
        return n.Arrangement(m);
    }

    /// <summary>
    /// 计算排列数。
    /// </summary>
    /// <param name="n">元素总数。</param>
    /// <param name="m">取出的元素个数。</param>
    /// <returns>排列数。</returns>
    public static decimal A(decimal n, decimal m)
    {
        return n.Arrangement(m);
    }

    /// <summary>
    /// 计算排列数。
    /// </summary>
    /// <param name="n">元素总数。</param>
    /// <param name="m">取出的元素个数。</param>
    /// <returns>排列数。</returns>
    public static float A(float n, float m)
    {
        return n.Arrangement(m);
    }

    /// <summary>
    /// 计算排列数。
    /// </summary>
    /// <param name="n">元素总数。</param>
    /// <param name="m">取出的元素个数。</param>
    /// <returns>排列数。</returns>
    public static double A(double n, double m)
    {
        return n.Arrangement(m);
    }

    #endregion 排列

    #region 组合

    /// <summary>
    /// 计算组合数。
    /// </summary>
    /// <remarks>
    /// 组合：从n个不同元素中，任取m(m≤n)个元素并成一组，叫做从n个不同元素中取出m个元素的一个组合。
    /// </remarks>
    /// <param name="n">元素总数。</param>
    /// <param name="m">取出的元素个数。</param>
    /// <returns></returns>
    public static decimal C(decimal n, decimal m)
    {
        return n.Combination(m);
    }

    /// <summary>
    /// 计算组合数。
    /// </summary>
    /// <remarks>
    /// 组合：从n个不同元素中，任取m(m≤n)个元素并成一组，叫做从n个不同元素中取出m个元素的一个组合。
    /// </remarks>
    /// <param name="n">元素总数。</param>
    /// <param name="m">取出的元素个数。</param>
    /// <returns></returns>
    public static double C(double n, double m)
    {
        return n.Combination(m);
    }

    /// <summary>
    /// 组合乘法。
    /// </summary>
    /// <remarks>
    /// 1 → 4 → 7
    /// 2 ↘ 5 ↘ 8
    /// 3    6    9
    ///
    /// 即：
    /// r1 = 1 × 4 × 7
    /// r2 = 1 × 4 × 8
    /// r3 = 1 × 4 × 9
    /// r4 = 1 × 5 × 7
    /// ……
    /// rn = 3 × 6 × 9
    /// </remarks>
    /// <param name="source"></param>
    /// <returns></returns>
    public static IEnumerable<double> CombinationMul(List<List<double>> source)
    {
        return source.CombinationMul();
    }

    #endregion 组合

    #region 概率计算

    /// <summary>
    /// 计算从样本空间 n 中取出 m 个元素，满足条件 f 的出现概率。
    /// </summary>
    /// <remarks>
    /// 示例：
    /// 红球 2 个，白球 1 个。
    /// 抽1个球时，出白球的概率。
    /// n = 红球总数 + 白球总数 = 3
    /// m = 抽取个数 = 1
    /// f = C(白球总数,抽取个数) = C(1,1)
    /// P = C(白球总数,抽取个数)÷C(球的总数,抽取个数) = f ÷ C(3,1) = C(1,1)÷C(3,1)=1/3
    /// </remarks>
    /// <param name="n">元素总数。</param>
    /// <param name="m">取出的元素个数。</param>
    /// <param name="f">有利场合结果值。</param>
    /// <returns></returns>
    public static double P(double n, double m, double f)
    {
        return f / n.Combination(m);
    }

    /// <summary>
    ///
    /// </summary>
    /// <param name="n">元素总数。</param>
    /// <param name="m">取出的元素个数。</param>
    /// <param name="combinations">组合集合C(n, m)。</param>
    /// <returns></returns>
    public static double Favorable(double n, double m, params (double, double)[] combinations)
    {
        var f = 1.0;
        var all = n;
        var lastNum = m;

        foreach (var parameter in combinations)
        {
            // 计算剩余的样本数。
            all -= parameter.Item1;

            // 扣除已抽取的个数。
            lastNum -= parameter.Item2;

            if (lastNum < 0)
                throw new MainApplicationException("超出最大取出个数");

            f *= C(parameter.Item1, parameter.Item2);
        }

        if (lastNum > 0)
        {
            f *= C(all, lastNum);
        }

        return f;
    }

    #endregion 概率计算

    #region --- 数值标准化 ---

    /// <summary>
    /// 数值标准化（归一化）。
    /// </summary>
    /// <param name="value">需要进行标准化的数值。</param>
    /// <param name="min">最小值。</param>
    /// <param name="max">最大值。</param>
    /// <returns>标准化后的数值。</returns>
    public static float Normalize(float value, float min, float max)
    {
        return (value - min) / (max - min);
    }

    #endregion

    #region --- 将 value 值映射到指定的区间范围内 ---

    /// <summary>
    /// 将 value 值映射到指定的区间范围内。
    /// </summary>
    /// <remarks>
    /// 示例:
    /// 将 a 映射到 -1 至 1 之间。
    /// 其中，a 的取值范围是 10 至 20。
    /// 当前 a = 12。
    /// 那么：
    /// value = 12。
    /// toA = -1
    /// toB = 1
    /// min = 10
    /// max = 20
    /// 结果是：-0.6
    /// </remarks>
    /// <param name="value">需要映射的值。</param>
    /// <param name="toA">区间左边的数。</param>
    /// <param name="toB">区间右边的值。</param>
    /// <param name="min">需要映射的值的最小值（即：value 值可能取的最小值）。</param>
    /// <param name="max">需要映射的值的最大值（即：value 值可能取的最大值）。</param>
    /// <returns></returns>
    public static float Remap(float value, float toA, float toB, float min, float max)
    {
        var y = toA + (toB - toA) / (max - min) * (value - min);

        return y;
    }

    #endregion
}
